package cheme.ProcessEditor.Tools;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PolylineConnection;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.ConnectionEditPart;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.KeyHandler;
import org.eclipse.gef.Request;
import org.eclipse.gef.RequestConstants;
import org.eclipse.gef.SharedCursors;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.requests.CreationFactory;
import org.eclipse.gef.requests.TargetRequest;
import org.eclipse.gef.tools.AbstractTool;
import org.eclipse.gef.tools.TargetingTool;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.widgets.Display;

/*
 * Mimics the MarqueeSelectionTool, creating a request for a new group.
 */
public class CreateGroupTool extends TargetingTool
{
	public static final String PROPERTY_CREATION_FACTORY = "Creation Factory";
	/*
	 * I keep this one around to leverage existing selectability of editparts and their figures
	 */
	private static final Request MARQUEE_REQUEST = new Request(RequestConstants.REQ_SELECTION);
	
	/*
	 * This is the actual request that gets emitted upon handleMouseUp.
	 */
	//private static final CreateGroupRequest CREATE_GROUP_REQUEST = new CreateGroupRequest();

	/**
	 * The property to be used in {@link AbstractTool#setProperties(java.util.Map)} 
	 * for {@link #setMarqueeBehavior(int)}.
	 */
	public static final Object PROPERTY_MARQUEE_BEHAVIOR = "marqueeBehavior"; //$NON-NLS-1$

	/**
	 * This behaviour selects nodes completely encompassed by the marquee
	 * rectangle. This is the default behaviour for this tool.
	 */
	public static final int BEHAVIOR_NODES_CONTAINED = new Integer(1).intValue();

	/**
	 * This behaviour selects connections that intersect the marquee rectangle.
	 */
	public static final int BEHAVIOR_CONNECTIONS_TOUCHED = new Integer(2).intValue();

	/**
	 * This behaviour selects nodes completely encompassed by the marquee
	 * rectangle, and all connections between those nodes.
	 */
	public static final int BEHAVIOR_NODES_AND_CONNECTIONS = new Integer(3).intValue();

	static final int DEFAULT_MODE = 0;

	static final int TOGGLE_MODE = 1;

	static final int APPEND_MODE = 2;

	private Figure marqueeRectangleFigure;

	private Set allChildren = new HashSet();

	private Collection selectedEditParts;

	private CreateGroupRequest targetRequest;

	private int marqueeBehavior = BEHAVIOR_NODES_CONTAINED;

	private int mode;
	
	private CreationFactory factory;
	
	/*
	 * The target for the create subprocess command. This will be a processmodelpart
	 * or another subprocesspart
	 */
	private EditPart targetEditPart;
	
	private static final int FLAG_LOCK_TARGET = AbstractTool.MAX_FLAG << 1;
	
	/**
	 * Creates a new MarqueeSelectionTool of default type {@link #BEHAVIOR_NODES_CONTAINED}.
	 */
	public CreateGroupTool()
	{
		setDefaultCursor(SharedCursors.CROSS);
		setUnloadWhenFinished(true);
	}
	
	/*--------------------------------------------------------------------------
	 * Request management
	 */
	
	protected CreateGroupRequest createTargetRequest()
	{
		CreateGroupRequest request = new CreateGroupRequest();
		request.setFactory(getFactory());
		return request;
	}
	
	/**
	 * Returns the creation factory used to create the new EditParts.
	 * @return the creation factory
	 */
	protected CreationFactory getFactory() {
		return factory;
	}
	
	/**
	 * Sets the creation factory used to create the new edit parts.
	 * @param factory the factory
	 */
	public void setFactory(CreationFactory factory) 
	{
		this.factory = factory;
	}
	
	protected CreateGroupRequest getTargetRequest()
	{
		if(targetRequest == null)
		{
			targetRequest = createTargetRequest();
		}
		
		return targetRequest;
	}
	
	private void setTargetRequest(CreateGroupRequest request)
	{
		targetRequest = request;
	}
	
	/*--------------------------------------------------------------------------
	 * Command management
	 */

	/**
	 * Executes the current command and selects the newly created object.  The button that was
	 * released to cause this creation is passed in, but since {@link #handleButtonDown(int)}
	 * goes into the invalid state if the button pressed is not button 1, this will always be 
	 * button 1.  
	 * 
	 * @param button the button that was pressed
	 */
	protected void performCreation(int button)
	{
		EditPartViewer viewer = getCurrentViewer();
		
		Rectangle bounds = getMarqueeSelectionRectangle();
		getTargetRequest().setSubProcessBounds(bounds.getCopy());
		getTargetRequest().setMoveDelta(bounds.getTopLeft().negate());
		getTargetRequest().setTargetEditPart(getTargetEditPart());
		
		executeCurrentCommand();
		
		selectAddedObject(viewer);
	}
	
	/**
	 * Execute the currently active command.
	 */
	protected void executeCurrentCommand()
	{
		setCurrentCommand(getCommand());
		
		Command curCommand = getCurrentCommand();
		
		if (curCommand != null && curCommand.canExecute())
		{
			executeCommand(curCommand);
		}
		
		setCurrentCommand(null);
		setTargetRequest(null);
	}
	
	/**
	 * Queries the target editpart for a command.
	 * @see org.eclipse.gef.tools.AbstractTool#getCommand()
	 */
	protected Command getCommand()
	{
		if (getTargetEditPart() == null)
		{
			return null;
		}
		
		return getTargetEditPart().getCommand(getTargetRequest());
	}
	
	/*--------------------------------------------------------------------------
	 * MarqueeTool
	 */
	
	private void calculateNewSelection(Collection newSelections, Collection deselections)
	{
		/*
		 * This tool only wants top level children, so forgoes the recursive
		 * collecting of children in the editpart tree.
		 */
		Rectangle marqueeRect = getMarqueeSelectionRectangle();
		EditPart editPart = getTargetEditPart();
		for(Iterator itr = editPart.getChildren().iterator(); itr.hasNext();)
		//for(Iterator itr = getAllChildren().iterator(); itr.hasNext();)
		{
			GraphicalEditPart child = (GraphicalEditPart)itr.next();
			IFigure figure = child.getFigure();
			if(!child.isSelectable() || child.getTargetEditPart(MARQUEE_REQUEST) != child || !isFigureVisible(figure) || !figure.isShowing())
			{
				continue;
			}

			/*
			 * Tranlslate to absolute for comparison with the marquee rectangle.
			 */
			Rectangle r = figure.getBounds().getCopy();
			figure.translateToAbsolute(r);
			
			
			boolean included = false;
			if(child instanceof ConnectionEditPart && marqueeRect.intersects(r))
			{
				/*
				 * Two tests here...
				 * The first does some kludge looking stuff to test connections...
				 * This might be important in the application of rule in terms of 
				 * the model. Visually, the PUs are what needs to be added to the 
				 * subsystem and the connections follow. Operationally, it is the 
				 * streams that contain the component info needed for solving.
				 * Of course the logic can simply be to test the PUs to which the 
				 * stream is connected to assess subprocess violations.
				 */
				
				Rectangle relMarqueeRect = Rectangle.SINGLETON;
				figure.translateToRelative(relMarqueeRect.setBounds(marqueeRect));
				included = ((PolylineConnection)figure).getPoints().intersects(relMarqueeRect);
			}
			else
			{
				/*
				 * Compare the childs absolute bounds againsr the marquee 
				 * absolute bounds.
				 */
				included = marqueeRect.contains(r);
			}

			if(included)
			{
				if(child.getSelected() == EditPart.SELECTED_NONE || getSelectionMode() != TOGGLE_MODE)
				{
					newSelections.add(child);
				}
				else
				{
					deselections.add(child);
				}
			}
		}

		if(marqueeBehavior == BEHAVIOR_NODES_AND_CONNECTIONS)
		{
			calculateConnections(newSelections, deselections);
		}
	}
	
	/**
	 * @see org.eclipse.gef.tools.AbstractTool#applyProperty(java.lang.Object, java.lang.Object)
	 */
	protected void applyProperty(Object key, Object value)
	{
		if(PROPERTY_MARQUEE_BEHAVIOR.equals(key))
		{
			if(value instanceof Integer)
			{
				setMarqueeBehavior(((Integer)value).intValue());
			}
			
			return;
		}
		
		if (PROPERTY_CREATION_FACTORY.equals(key))
		{
			if (value instanceof CreationFactory)
			{
				setFactory((CreationFactory)value);
			}
			
			return;
		}
		super.applyProperty(key, value);
	}
	
	private void calculateConnections(Collection newSelections, Collection deselections)
	{
		// determine the currently selected nodes minus the ones that are to be deselected
		Collection currentNodes = new HashSet();
		if(getSelectionMode() != DEFAULT_MODE)
		{
			// everything is deselected in default mode
			Iterator iter = getCurrentViewer().getSelectedEditParts().iterator();
			while(iter.hasNext())
			{
				EditPart selected = (EditPart)iter.next();
				if(!(selected instanceof ConnectionEditPart) && !deselections.contains(selected))
				{
					currentNodes.add(selected);
				}
			}
		}
		// add new connections to be selected to newSelections
		Collection connections = new ArrayList();
		for(Iterator nodes = newSelections.iterator(); nodes.hasNext();)
		{
			GraphicalEditPart node = (GraphicalEditPart)nodes.next();
			for(Iterator itr = node.getSourceConnections().iterator(); itr.hasNext();)
			{
				ConnectionEditPart sourceConn = (ConnectionEditPart)itr.next();
				if(sourceConn.getSelected() == EditPart.SELECTED_NONE && (newSelections.contains(sourceConn.getTarget()) || currentNodes.contains(sourceConn.getTarget())))
				{
					connections.add(sourceConn);
				}
			}
			
			for(Iterator itr = node.getTargetConnections().iterator(); itr.hasNext();)
			{
				ConnectionEditPart targetConn = (ConnectionEditPart)itr.next();
				if(targetConn.getSelected() == EditPart.SELECTED_NONE && (newSelections.contains(targetConn.getSource()) || currentNodes.contains(targetConn.getSource())))
				{
					connections.add(targetConn);
				}
			}
		}
		
		newSelections.addAll(connections);
		// add currently selected connections that are to be deselected to
		// deselections
		connections = new HashSet();
		for(Iterator nodes = deselections.iterator(); nodes.hasNext();)
		{
			GraphicalEditPart node = (GraphicalEditPart)nodes.next();
			for(Iterator itr = node.getSourceConnections().iterator(); itr.hasNext();)
			{
				ConnectionEditPart sourceConn = (ConnectionEditPart)itr.next();
				if(sourceConn.getSelected() != EditPart.SELECTED_NONE)
				{
					connections.add(sourceConn);
				}
			}
			
			for(Iterator itr = node.getTargetConnections().iterator(); itr.hasNext();)
			{
				ConnectionEditPart targetConn = (ConnectionEditPart)itr.next();
				if(targetConn.getSelected() != EditPart.SELECTED_NONE)
				{
					connections.add(targetConn);
				}
			}
		}
		deselections.addAll(connections);
	}

	/**
	 * Erases feedback if necessary and puts the tool into the terminal state.
	 */
	public void deactivate()
	{
		if(isInState(STATE_DRAG_IN_PROGRESS))
		{
			eraseMarqueeFeedback();
			eraseTargetFeedback();
		}
		
		super.deactivate();
		allChildren.clear();
		setState(STATE_TERMINAL);
	}
	
	protected void showTargetFeedback()
	{
//		for(Iterator itr = selectedEditParts.iterator(); itr.hasNext();)
//		{
//			EditPart editPart = (EditPart)itr.next();
//			editPart.showTargetFeedback(getTargetRequest());
//		}
	}

	private void eraseMarqueeFeedback()
	{
		if(marqueeRectangleFigure != null)
		{
			removeFeedback(marqueeRectangleFigure);
			marqueeRectangleFigure = null;
		}
	}

	protected void eraseTargetFeedback()
	{
		if(selectedEditParts == null)
			return;
		Iterator oldEditParts = selectedEditParts.iterator();
		while(oldEditParts.hasNext())
		{
			EditPart editPart = (EditPart)oldEditParts.next();
			editPart.eraseTargetFeedback(getTargetRequest());
		}
	}

	private Set getAllChildren()
	{
		if(allChildren.isEmpty())
		{
			getAllChildren(getCurrentViewer().getRootEditPart(), allChildren);
		}
		
		return allChildren;
	}

	/*
	 * Recurssively collect all children in the tree.
	 */
	private void getAllChildren(EditPart editPart, Set allChildren)
	{
		List children = editPart.getChildren();
		for(int i = 0; i < children.size(); i++)
		{
			GraphicalEditPart child = (GraphicalEditPart)children.get(i);
			if(marqueeBehavior == BEHAVIOR_NODES_CONTAINED || marqueeBehavior == BEHAVIOR_NODES_AND_CONNECTIONS)
			{
				allChildren.add(child);
			}
			if(marqueeBehavior == BEHAVIOR_CONNECTIONS_TOUCHED)
			{
				allChildren.addAll(child.getSourceConnections());
				allChildren.addAll(child.getTargetConnections());
			}
			getAllChildren(child, allChildren);
		}
	}

	/**
	 * @see org.eclipse.gef.tools.AbstractTool#getCommandName()
	 */
	protected String getCommandName()
	{
		return REQ_SELECTION;
	}

	/**
	 * @see org.eclipse.gef.tools.AbstractTool#getDebugName()
	 */
	protected String getDebugName()
	{
		return "Marquee Tool: " + marqueeBehavior;//$NON-NLS-1$
	}

	private IFigure getMarqueeFeedbackFigure()
	{
		if(marqueeRectangleFigure == null)
		{
			marqueeRectangleFigure = new MarqueeRectangleFigure();
			addFeedback(marqueeRectangleFigure);
		}
		return marqueeRectangleFigure;
	}

	private Rectangle getMarqueeSelectionRectangle()
	{
		return new Rectangle(getStartLocation(), getLocation());
	}

	private int getSelectionMode()
	{
		return mode;
	}

	/**
	 * @see org.eclipse.gef.tools.AbstractTool#handleButtonDown(int)
	 */
	protected boolean handleButtonDown(int button)
	{
		updateTargetUnderMouse();
		
		lockTargetEditPart(getTargetEditPart());
		
		if(!isGraphicalViewer())
		{
			return true;
		}
		
		if(button != 1)
		{
			setState(STATE_INVALID);
			handleInvalidInput();
		}
		
		if(stateTransition(STATE_INITIAL, STATE_DRAG_IN_PROGRESS))
		{
			if(getCurrentInput().isModKeyDown(SWT.MOD1))
			{
				setSelectionMode(TOGGLE_MODE);
			}
			else if(getCurrentInput().isShiftKeyDown())
			{
				setSelectionMode(APPEND_MODE);
			}
			else
			{
				setSelectionMode(DEFAULT_MODE);
			}
		}
		return true;
	}

	/**
	 * @see org.eclipse.gef.tools.AbstractTool#handleButtonUp(int)
	 */
	protected boolean handleButtonUp(int button)
	{
		if(stateTransition(STATE_DRAG_IN_PROGRESS, STATE_TERMINAL))
		{
			eraseTargetFeedback();
			eraseMarqueeFeedback();
			
			unlockTargetEditPart();

			//performMarqueeSelect();
			
			{
				Collection newSelections = new LinkedHashSet();
				Collection deselections = new HashSet();
				calculateNewSelection(newSelections, deselections);
				getTargetRequest().setEditParts(new ArrayList(newSelections));
			}
			performCreation(button);
		}
		
		setState(STATE_TERMINAL); //might want to do this?
		
		handleFinished();
		return true;
	}

	/**
	 * @see org.eclipse.gef.tools.AbstractTool#handleDragInProgress()
	 */
	protected boolean handleDragInProgress()
	{
		if(isInState(STATE_DRAG | STATE_DRAG_IN_PROGRESS))
		{
			showMarqueeFeedback();
			eraseTargetFeedback();
			calculateNewSelection(selectedEditParts = new ArrayList(), new ArrayList());
			showTargetFeedback();
		}
		return true;
	}

	/**
	 * @see org.eclipse.gef.tools.AbstractTool#handleFocusLost()
	 */
	protected boolean handleFocusLost()
	{
		if(isInState(STATE_DRAG | STATE_DRAG_IN_PROGRESS))
		{
			handleFinished();
			return true;
		}
		return false;
	}

	/**
	 * This method is called when mouse or keyboard input is invalid and erases
	 * the feedback.
	 * 
	 * @return <code>true</code>
	 */
	protected boolean handleInvalidInput()
	{
		eraseTargetFeedback();
		eraseMarqueeFeedback();
		return true;
	}

	/**
	 * Handles high-level processing of a key down event. KeyEvents are
	 * forwarded to the current viewer's {@link KeyHandler}, via
	 * {@link KeyHandler#keyPressed(KeyEvent)}.
	 * 
	 * @see AbstractTool#handleKeyDown(KeyEvent)
	 */
	protected boolean handleKeyDown(KeyEvent e)
	{
		if(super.handleKeyDown(e))
		{
			return true;
		}
		
		if(getCurrentViewer().getKeyHandler() != null)
		{
			return getCurrentViewer().getKeyHandler().keyPressed(e);
		}
		
		return false;
	}

	private boolean isFigureVisible(IFigure fig)
	{
		Rectangle figBounds = fig.getBounds().getCopy();
		IFigure walker = fig.getParent();
		while(!figBounds.isEmpty() && walker != null)
		{
			walker.translateToParent(figBounds);
			figBounds.intersect(walker.getBounds());
			walker = walker.getParent();
		}
		return !figBounds.isEmpty();
	}

	private boolean isGraphicalViewer()
	{
		return getCurrentViewer() instanceof GraphicalViewer;
	}

	/**
	 * MarqueeSelectionTool is only interested in GraphicalViewers, not
	 * TreeViewers.
	 * 
	 * @see org.eclipse.gef.tools.AbstractTool#isViewerImportant(org.eclipse.gef.EditPartViewer)
	 */
	protected boolean isViewerImportant(EditPartViewer viewer)
	{
		return viewer instanceof GraphicalViewer;
	}

//	private void performMarqueeSelect()
//	{
//		EditPartViewer viewer = getCurrentViewer();
//		Collection newSelections = new LinkedHashSet(), deselections = new HashSet();
//		calculateNewSelection(newSelections, deselections);
//		if(getSelectionMode() != DEFAULT_MODE)
//		{
//			newSelections.addAll(viewer.getSelectedEditParts());
//			newSelections.removeAll(deselections);
//		}
//		viewer.setSelection(new StructuredSelection(newSelections.toArray()));
//	}

	/**
	 * Sets the type of parts that this tool will select. This method should
	 * only be invoked once: when the tool is being initialized.
	 * 
	 * @param type
	 *            {@link #BEHAVIOR_CONNECTIONS_TOUCHED} or
	 *            {@link #BEHAVIOR_NODES_CONTAINED} or
	 *            {@link #BEHAVIOR_NODES_AND_CONNECTIONS}
	 */
	public void setMarqueeBehavior(int type)
	{
		if(type != BEHAVIOR_CONNECTIONS_TOUCHED && type != BEHAVIOR_NODES_CONTAINED && type != BEHAVIOR_NODES_AND_CONNECTIONS)
		{
			throw new IllegalArgumentException("Invalid marquee behaviour specified."); //$NON-NLS-1$
		}
		
		marqueeBehavior = type;
	}

	private void setSelectionMode(int mode)
	{
		this.mode = mode;
	}

	/**
	 * @see org.eclipse.gef.Tool#setViewer(org.eclipse.gef.EditPartViewer)
	 */
	public void setViewer(EditPartViewer viewer)
	{
		if(viewer == getCurrentViewer())
		{
			return;
		}
		
		super.setViewer(viewer);
		
		if(viewer instanceof GraphicalViewer)
		{
			setDefaultCursor(SharedCursors.CROSS);
		}
		else
		{
			setDefaultCursor(SharedCursors.NO);
		}
	}

	private void showMarqueeFeedback()
	{
		Rectangle rect = getMarqueeSelectionRectangle().getCopy();
		getMarqueeFeedbackFigure().translateToRelative(rect);
		getMarqueeFeedbackFigure().setBounds(rect);
	}

	class MarqueeRectangleFigure extends Figure
	{
		private static final int DELAY = 110; // animation delay in millisecond

		private int offset = 0;

		private boolean schedulePaint = true;

		/**
		 * @see org.eclipse.draw2d.Figure#paintFigure(org.eclipse.draw2d.Graphics)
		 */
		protected void paintFigure(Graphics graphics)
		{
			Rectangle bounds = getBounds().getCopy();
			graphics.translate(getLocation());

			graphics.setXORMode(true);
			graphics.setForegroundColor(ColorConstants.white);
			graphics.setBackgroundColor(ColorConstants.black);

			graphics.setLineStyle(Graphics.LINE_DOT);

			int[] points = new int[6];

			points[0] = 0 + offset;
			points[1] = 0;
			points[2] = bounds.width - 1;
			points[3] = 0;
			points[4] = bounds.width - 1;
			points[5] = bounds.height - 1;

			graphics.drawPolyline(points);

			points[0] = 0;
			points[1] = 0 + offset;
			points[2] = 0;
			points[3] = bounds.height - 1;
			points[4] = bounds.width - 1;
			points[5] = bounds.height - 1;

			graphics.drawPolyline(points);

			graphics.translate(getLocation().getNegated());

			if(schedulePaint)
			{
				Display.getCurrent().timerExec(DELAY, new Runnable()
				{
					public void run()
					{
						offset++;
						if(offset > 5)
						{
							offset = 0;
						}

						schedulePaint = true;
						repaint();
					}
				});
			}

			schedulePaint = false;
		}
	}
	
	/*
	 * Add the newly created object to the viewer's selected objects.
	 */
	private void selectAddedObject(EditPartViewer viewer)
	{
//		final Object model = getCreateRequest().getNewObject();
//		if (model == null || viewer == null)
//			return;
//		Object editpart = viewer.getEditPartRegistry().get(model);
//		if (editpart instanceof EditPart)
//		{
//			//Force the new object to get positioned in the viewer. 
//			viewer.flush();
//			viewer.select((EditPart)editpart);
//		}
	}
	
	/**
	 * Returns <code>null</code> or the current target editpart.
	 * @return <code>null</code> or a target part
	 */
	protected EditPart getTargetEditPart()
	{
		return targetEditPart;
	}
	
	/**
	 * Sets the target editpart.  If the target editpart is changing, this method will call
	 * {@link #handleExitingEditPart()} for the previous target if not <code>null</code>, and
	 * {@link #handleEnteredEditPart()} for the new target, if not <code>null</code>.
	 * @param editpart the new target
	 */
	protected void setTargetEditPart(EditPart editpart)
	{
		if (editpart != targetEditPart)
		{
			if (targetEditPart != null)
			{
				handleExitingEditPart();
			}
			
			targetEditPart = editpart;
			
			if (getTargetRequest() instanceof TargetRequest)
			{
				((TargetRequest)getTargetRequest()).setTargetEditPart(targetEditPart);
			}
			
			handleEnteredEditPart();
		}
	}
	
	/**
	 * Called whenever the target editpart is about to change.  By default, hover is reset, in
	 * the case that a hover was showing something, and the target being exited is asked to
	 * erase its feedback.
	 * @return <code>true</code>
	 */
	protected boolean handleExitingEditPart()
	{
		resetHover();
		eraseTargetFeedback();
		return true;
	}
	
	/**
	 * Called whenever the target editpart has changed. By default, the target request is
	 * updated, and the new target is asked to show feedback.  Subclasses may extend this
	 * method if needed.
	 * @return <code>true</code>
	 */
	protected boolean handleEnteredEditPart()
	{
		updateTargetRequest();
		showTargetFeedback();
		return true;
	}
	
	void resetHover()
	{
		if (isHoverActive())
		{
			handleHoverStop();
		}
		setHoverActive(false);
	}
	
	/**
	 * Called from resetHover() iff hover is active.  Subclasses may extend this method to
	 * handle the hover stop event.  Returns <code>true</code> if something was done in
	 * response to the call.
	 * @see AbstractTool#isHoverActive()
	 * @return <code>true</code> if the hover stop is processed in some way
	 */
	protected boolean handleHoverStop()
	{
		return false;
	}
	
	/**
	 * Subclasses should override to update the target request.
	 */
	protected void updateTargetRequest() { }
	
	/**
	 * Updates the target editpart and returns <code>true</code> if the target changes.  The
	 * target is updated by using the target conditional and the target request.  If the
	 * target has been locked, this method does nothing and returns <code>false</code>.
	 * @return <code>true</code> if the target was changed
	 */
	protected boolean updateTargetUnderMouse()
	{
		if (!isTargetLocked())
		{
			EditPart editPart = getCurrentViewer().findObjectAtExcluding(getLocation(), getExclusionSet(), getTargetingConditional());
			
			if (editPart != null)
			{
				editPart = editPart.getTargetEditPart(getTargetRequest());
			}
			
			boolean changed = getTargetEditPart() != editPart;
			setTargetEditPart(editPart);
			
			return changed;
		}
		else
		{
			return false;
		}
	}
	
	/**
	 * Return <code>true</code> if the current target is locked.
	 * @see #lockTargetEditPart(EditPart)
	 * @return <code>true</code> if the target is locked
	 */
	protected boolean isTargetLocked()
	{
		return getFlag(FLAG_LOCK_TARGET);
	}
	
	/**
	 * Returns a List of objects that should be excluded as potential targets for the
	 * operation.
	 * @return the list of objects to be excluded as targets
	 */
	protected Collection getExclusionSet()
	{
		return Collections.EMPTY_LIST;
	}
	
	/**
	 * Returns the conditional object used for obtaining the target editpart from the current
	 * viewer.  By default, a conditional is returned that tests whether an editpart at the
	 * current mouse location indicates a target for the operation's request, using
	 * {@link EditPart#getTargetEditPart(Request)}.  If <code>null</code> is returned, then
	 * the conditional fails, and the search continues.
	 * @see EditPartViewer#findObjectAtExcluding(Point, Collection, 
	 * 													EditPartViewer.Conditional)
	 * @return the targeting conditional
	 */
	protected EditPartViewer.Conditional getTargetingConditional()
	{
		return new EditPartViewer.Conditional()
		{
			public boolean evaluate(EditPart editpart)
			{
				return editpart.getTargetEditPart(getTargetRequest()) != null;
			}
		};
	}
}
